using System;
using Mapsui.Extensions;
using Mapsui.Limiting;

namespace Mapsui.InfiniteMap;

/// <summary>
/// 高度限制
/// </summary>
public class HeightLimiter : IViewportLimiter
{
    /// <summary>
    /// 
    /// </summary>
    /// <param name="viewport"></param>
    /// <param name="panBounds">完整地图的尺寸</param>
    /// <param name="zoomBounds">缩放限制，即最大分辨率和最小分辨率</param>
    /// <returns></returns>
    /// <exception cref="Exception"></exception>
    public Viewport Limit(Viewport viewport, MRect? panBounds, MMinMax? zoomBounds)
    {
        if (zoomBounds is null) return viewport;

        // Map.Navigator 设置的缩放限制
        viewport = LimitResolution(viewport, zoomBounds);

        // 自定义的缩放
        return LimitExtent(viewport, panBounds, zoomBounds);
    }

    private static Viewport LimitResolution(Viewport viewport, MMinMax? zoomBounds)
    {
        if (zoomBounds is null) return viewport;

        if (zoomBounds.Min > viewport.Resolution) return viewport with { Resolution = zoomBounds.Min };
        if (zoomBounds.Max < viewport.Resolution) return viewport with { Resolution = zoomBounds.Max };

        return viewport;
    }

    private static Viewport LimitExtent(Viewport viewport, MRect? panBounds, MMinMax? zoomBounds)
    {
        if (panBounds is null) return viewport;

        // 设置分辨率，高充满窗口（即上下不留空白）
        double viewportFillingResolution = panBounds.Height / viewport.Height;
        if (viewportFillingResolution < viewport.Resolution)
            viewport = viewport with { Resolution = viewportFillingResolution };

        var extent = viewport.ToExtent();
        
        // 限制上下移动(如果上方脱离边界，就上移，如果下方脱离边界，就下移，因为已经按照高度设置了分辨率，所以不会上下都脱离边界）
        var y = viewport.CenterY;
        if (extent.Top > panBounds.Top)
            y += panBounds.Top - extent.Top;
        else if (extent?.Bottom < panBounds.Bottom)
            y += panBounds.Bottom - extent.Bottom;
        
        // 由于宽度方向无限循环，因此为了避免数值无限大，需要对地球宽度取余
        var x = viewport.CenterX % (panBounds.Width );
        
        viewport = viewport with { CenterX = x, CenterY = y };
        return viewport;
    }
}